package edu.um.expression;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import edu.um.analyzer.exception.InvalidArrayIndexException;
import edu.um.analyzer.exception.InvalidVariableReferenceException;
import edu.um.analyzer.exception.VariableNotDeclaredException;
import edu.um.io.Console;
import edu.um.token.Token;
import edu.um.token.TokenKind;
import edu.um.variable.ArrayVariable;
import edu.um.variable.Range;
import edu.um.variable.RecordVariable;
import edu.um.variable.Variable;
import edu.um.variable.VariableInArray;
import edu.um.variable.VariableType;

/**
 * The {@link ExpressionTreeNode} factory.
 *
 * @author diego.gluj
 */
public class ExpressionTreeNodeFactory {

  public static Map<String, Variable> variableMap = null;

  public static final char[] OPERATOR_PRECEDENCE = {'+', '-', '*', '^', '!'};

  /**
   * @param expression. The List of Token. It cannot be null.
   * @return a new {@link ExpressionTreeNode}.
   * @throws InvalidVariableReferenceException
   * @throws InvalidArrayIndexException
   * @throws VariableNotDeclaredException
   */
  public static ExpressionTreeNode createExpressionTreeNode(
      Expression expression) throws InvalidVariableReferenceException, InvalidArrayIndexException, VariableNotDeclaredException {
    Console.debug("Create Expression Tree: " + expression);
    if (expression == null) {
      throw new IllegalArgumentException("The expression cannot be null");
    }
    if (expression.isEmpty()) {
      return null;
    }
    Token firstToken = expression.get(0);
    if (expression.size() == 1 && firstToken.getKind() != TokenKind.ID) {
      return new ExpressionTreeNode(firstToken);
    }
    for (char operator : OPERATOR_PRECEDENCE) {
      ExpressionTreeNode expressionNode = parse(expression, operator);
      if (expressionNode != null) {
        return expressionNode;
      }
    }
    if (firstToken.getKind() == TokenKind.ABRE_PARENTESIS
        && expression.get(expression.size() - 1).getKind() == TokenKind.CIERRA_PARENTESIS) {
      return createExpressionTreeNode(new Expression(expression.subList(1,
          expression.size() - 1)));
    }
    if (firstToken.getKind() == TokenKind.ID) {
      if (variableMap == null) {
        throw new IllegalArgumentException("Variable map has not been set");
      }
      Variable variable = variableMap.get(firstToken.getValue());
      String variableName = firstToken.getValue();
      if (variable == null) {
        throw new VariableNotDeclaredException(firstToken);
      }
      variable.setUsed(true);
      if (variable.getType() == VariableType.REGISTRO) {
        Token fieldToken = expression.get(2);
        variable = ((RecordVariable) variable).getField(fieldToken
            .getValue());
        variableName += "." + fieldToken.getValue();
      } else if (variable.getType() == VariableType.ARRAY) {
        ArrayVariable arrayVariable = (ArrayVariable) variable;
        if (expression.size() < 4) {
          throw new InvalidArrayIndexException(firstToken);
        }
        int index = 2;
        List<Range> ranges = arrayVariable.getRanges();
        List<ExpressionTreeNode> indexExpressionTrees = new ArrayList<ExpressionTreeNode>();
        Token token = expression.get(index);
        Expression indexExpression = new Expression();
        while (token.getKind() != TokenKind.CIERRA_CORCHETE) {
          if (token.getKind() == TokenKind.COMA) {
            // Check if not exceding the number of dimensions
            if (indexExpressionTrees.size() == ranges.size() - 1) {
              throw new InvalidArrayIndexException(token);
            }
            indexExpressionTrees.add(createExpressionTreeNode(indexExpression));
            indexExpression = new Expression();
          } else {
            indexExpression.add(token);
          }
          index++;
          token = expression.get(index);
        }
        indexExpressionTrees.add(createExpressionTreeNode(indexExpression));
        if (indexExpressionTrees.size() < ranges.size()) {
          throw new InvalidArrayIndexException(token);
        }
        variable = new VariableInArray(variableName + indexExpressionTrees,
            arrayVariable, indexExpressionTrees);
      } else {
        if (expression.size() > 1) {
          throw new InvalidVariableReferenceException(expression.get(1));
        }
      }
      if (variable == null) {
        throw new VariableNotDeclaredException(expression);
      }
      return new ExpressionTreeNode(firstToken, variable);
    }
    return null;
  }

  /**
   * @param token
   * @param leftExpression
   * @param rightExpression
   * @return A {@link ExpressionTreeNode}.
   * @throws InvalidVariableReferenceException
   * @throws VariableNotDeclaredException
   * @throws InvalidArrayIndexException
   */
  public static ExpressionTreeNode createExpressionTreeNode(Token token,
      Expression leftExpression, Expression rightExpression) throws InvalidVariableReferenceException, InvalidArrayIndexException, VariableNotDeclaredException {
    ExpressionTreeNode leftNode = createExpressionTreeNode(leftExpression);
    ExpressionTreeNode rightNode = createExpressionTreeNode(rightExpression);
    return new ExpressionTreeNode(token, leftNode, rightNode);
  }

  /**
   * @param expression
   * @param operator
   * @return the {@link ExpressionTreeNode} if the operator is found in the
   *         root or null if it's not found.
   * @throws InvalidVariableReferenceException
   * @throws VariableNotDeclaredException
   * @throws InvalidArrayIndexException
   */
  private static ExpressionTreeNode parse(Expression expression, char operator) throws InvalidVariableReferenceException, InvalidArrayIndexException, VariableNotDeclaredException {
    ListIterator<Token> iterator = expression.listIterator();
    int parenthesis = 0;
    int brackets = 0;
    while (iterator.hasNext()) {
      Token token = iterator.next();
      if (brackets == 0 && parenthesis == 0
          && token.getValue().equals(Character.toString(operator))) {
        return createExpressionTreeNode(
            token,
            expression.subList(0, iterator.previousIndex()),
                expression.subList(iterator.nextIndex(), expression.size()));
      }
      if (token.getKind() == TokenKind.ABRE_PARENTESIS) {
        parenthesis++;
      }
      if (token.getKind() == TokenKind.CIERRA_PARENTESIS) {
        parenthesis--;
      }
      if (token.getKind() == TokenKind.ABRE_CORCHETE) {
        brackets++;
      }
      if (token.getKind() == TokenKind.CIERRA_CORCHETE) {
        brackets--;
      }
    }
    return null;
  }

  /**
   * @param token
   * @param leftTokenList
   * @param rightTokenList
   * @return the {@link ExpressionTreeNode} if the operator is found in the
   *         root or null if it's not found.
   * @throws InvalidVariableReferenceException
   * @throws VariableNotDeclaredException
   * @throws InvalidArrayIndexException
   */
  private static ExpressionTreeNode createExpressionTreeNode(Token token,
      List<Token> leftTokenList, List<Token> rightTokenList) throws InvalidVariableReferenceException, InvalidArrayIndexException, VariableNotDeclaredException {
    return createExpressionTreeNode(token, new Expression(leftTokenList),
        new Expression(rightTokenList));
  }

  public static void setVariablemap(Map<String, Variable> theVariablemap) {
    variableMap = theVariablemap;
  }
}
